/*!
    \copyright  Copyright (c) 2019 - 2025 Qualcomm Technologies International, Ltd.
                All Rights Reserved.
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \file
    \ingroup    gatt_connect
    \brief      Application support for observice GATT connect/disconnect
*/

#include "gatt_connect.h"
#include "gatt_connect_observer.h"
#include "gatt_connect_config.h"
#include "gatt_service_discovery.h"

#include <panic.h>
#include "logging.h"
#include "gatt.h"

#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING

/*! Number of supported Connections */
#define MAX_GATT_CONNECT_REMOTE_DB_INSTANCES (2 + GATT_CONNECT_MAX_ACCESSORY_DEVICE_LE_CONNECTIONS)

/*! Structure to hold data associated remote database change observer instance */
typedef struct
{
    gatt_cid_t cid;
    /* 0 if ready for service rediscovery. Non-zero if there is pending acknowledgements from clients to receive or
       if profile is not yet ready. */
    uint8 ready_for_service_discovery;
} gatt_connect_remote_db_observer_instance_t;

/*! Structure to hold data associated with remote database change observers */
typedef struct
{
    uint8 number_registered_clients;
    gatt_connect_remote_db_observer_instance_t instances[MAX_GATT_CONNECT_REMOTE_DB_INSTANCES];
    gatt_connect_db_change_callback_t callbacks[MAX_NUMBER_GATT_DB_CHANGE_OBSERVERS];
} gatt_connect_remote_db_observer_registry_t;
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */

typedef struct
{
    const gatt_connect_observer_callback_t *callbacks[MAX_NUMBER_GATT_CONNECT_OBSERVERS];
    unsigned number_registered;
} gatt_connect_observer_registry_t;

static gatt_connect_observer_registry_t gatt_connect_observer_registry = {0};

#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
static gatt_connect_remote_db_observer_registry_t gatt_connect_remote_db_observer_registry = {0};

/*! \brief Get DB observer instance for given cid. Returns NULL if it can not find matching instance */
static gatt_connect_remote_db_observer_instance_t* gattConnect_GetDbObserverInstanceForCid(gatt_cid_t cid)
{
    gatt_connect_remote_db_observer_instance_t *db_observer_inst = NULL;

    ARRAY_FOREACH(db_observer_inst, gatt_connect_remote_db_observer_registry.instances)
    {
        if (db_observer_inst->cid == cid)
        {
            return db_observer_inst;
        }
    }

    return NULL;
}

/*! \brief Create a DB observer instance for given cid. Returns NULL if it couldn't create an instance */
static gatt_connect_remote_db_observer_instance_t* gattConnect_CreateObserverInstanceForCid(gatt_cid_t cid)
{
    gatt_connect_remote_db_observer_instance_t *db_observer_inst = gattConnect_GetDbObserverInstanceForCid(INVALID_CID);

    if (db_observer_inst != NULL)
    {
        /* Associate cid to the instance */
         db_observer_inst->cid = cid;
    }

    return db_observer_inst;
}

/*! \brief Reset the given db observer instance */
static void gattConnect_ResetDbObserverInstance(gatt_connect_remote_db_observer_instance_t *db_observer_inst)
{
    if (db_observer_inst != NULL)
    {
        memset(db_observer_inst, 0, sizeof(gatt_connect_remote_db_observer_instance_t));
        db_observer_inst->cid = INVALID_CID;
    }
}

/*! \brief Reset the db observer instance for given cid */
static void gattConnect_ResetDbObserverInstanceForCid(gatt_cid_t cid)
{
    gatt_connect_remote_db_observer_instance_t *db_observer_inst = gattConnect_GetDbObserverInstanceForCid(cid);

    gattConnect_ResetDbObserverInstance(db_observer_inst);
}

static void gattConnect_StartServiceDiscoveryIfReady(gatt_cid_t cid)
{
    gatt_connect_remote_db_observer_instance_t *db_observer_inst = gattConnect_GetDbObserverInstanceForCid(cid);

    if (db_observer_inst == NULL)
    {
        /* No matching instance */
        return;
    }

    /* Decrement counter as we received an acknowledgement/in sync */
    db_observer_inst->ready_for_service_discovery--;

    /* Trigger service rediscovery if profiles are ready and all registered clients acknowledged to the remote db change
       indication */
    if (db_observer_inst->ready_for_service_discovery == 0)
    {
        GattServiceDiscovery_StartServiceDiscovery(cid);

        /* Reset the instance */
        gattConnect_ResetDbObserverInstance(db_observer_inst);
    }
}

static void gattConnect_DbObserverInit(void)
{
    gatt_connect_remote_db_observer_instance_t *db_observer_inst = NULL;

    memset(&gatt_connect_remote_db_observer_registry, 0, sizeof(gatt_connect_remote_db_observer_registry));

    ARRAY_FOREACH(db_observer_inst, gatt_connect_remote_db_observer_registry.instances)
    {
        db_observer_inst->cid = INVALID_CID;
    }
}

void GattConnect_ObserverNotifyOnRemoteDbChange(gatt_cid_t cid, bool out_of_sync)
{
    unsigned index = 0;
    gatt_connect_remote_db_observer_instance_t *db_observer_inst = NULL;

    if (out_of_sync)
    {
        db_observer_inst = gattConnect_CreateObserverInstanceForCid(cid);

        if (db_observer_inst == NULL)
        {
            /* Create instance failed. Not expected to happen ideally */
            DEBUG_LOG_WARN("GattConnect_ObserverNotifyOnRemoteDbChange couldn't create instance for cid 0x%x", cid);
            return;
        }

        /* Set ready_for_service_discovery as 1 in out of sync (This later gets decremented when we receive in sync) */
        db_observer_inst->ready_for_service_discovery = 1;

        while (index < ARRAY_DIM(gatt_connect_remote_db_observer_registry.callbacks))
        {
            if (gatt_connect_remote_db_observer_registry.callbacks[index] != NULL)
            {
                db_observer_inst->ready_for_service_discovery++;
                gatt_connect_remote_db_observer_registry.callbacks[index](cid);
            }
            index++;
        }
    }
    else
    {
        /* Remote db have changed and in sync, so we are ready for service rediscovery. Decrement counter */
        gattConnect_StartServiceDiscoveryIfReady(cid);
    }
}

void GattConnect_RegisterRemoteDbChangeCallback(gatt_connect_db_change_callback_t remote_db_change_callback)
{
    if(gatt_connect_remote_db_observer_registry.number_registered_clients < MAX_NUMBER_GATT_DB_CHANGE_OBSERVERS)
    {
        gatt_connect_remote_db_observer_registry.callbacks[gatt_connect_remote_db_observer_registry.number_registered_clients] = remote_db_change_callback;
        gatt_connect_remote_db_observer_registry.number_registered_clients++;
    }
    else
    {
        /*  No space to store a new observer, as this is a fixed number at compile time.
            Cause a Panic so that more space can be reserved for observers.
        */
        Panic();
    }
}

void GattConnect_AcknowledgeRemoteDbChange(gatt_cid_t cid)
{
    DEBUG_LOG_INFO("GattConnect_AcknowledgeRemoteDbChange 0x%x", cid);

    /* Trigger service discovery if all clients acknowledged and we are in sync */
    gattConnect_StartServiceDiscoveryIfReady(cid);
}

#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */

void GattConnect_RegisterObserver(const gatt_connect_observer_callback_t * const callback)
{
    if(gatt_connect_observer_registry.number_registered < MAX_NUMBER_GATT_CONNECT_OBSERVERS)
    {
        /* The register requires that all callback functions have been supplied */
        PanicNull((void*)callback);
        PanicNull((void*)callback->OnConnection);
        PanicNull((void*)callback->OnDisconnection);
        
        gatt_connect_observer_registry.callbacks[gatt_connect_observer_registry.number_registered] = callback;
        gatt_connect_observer_registry.number_registered++;
    }
    else
    {
        /*  No space to store a new observer, as this is a fixed number at compile time.
            Cause a Panic so that more space can be reserved for observers. 
        */
        Panic();
    }
}

void GattConnect_ObserverNotifyOnConnection(gatt_cid_t cid)
{
    unsigned index = 0;

    while (index < gatt_connect_observer_registry.number_registered)
    {
        const gatt_connect_observer_callback_t * callback = gatt_connect_observer_registry.callbacks[index];

        callback->OnConnection(cid);
        
        index++;
    }
}

void GattConnect_ObserverNotifyOnDisconnection(gatt_cid_t cid)
{
    unsigned index = 0;

    while (index < gatt_connect_observer_registry.number_registered)
    {
        const gatt_connect_observer_callback_t * callback = gatt_connect_observer_registry.callbacks[index];

        callback->OnDisconnection(cid);

        index++;
    }

#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
    gattConnect_ResetDbObserverInstanceForCid(cid);
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */
}

void GattConnect_ObserverNotifyOnDisconnectRequested(gatt_cid_t cid, gatt_connect_disconnect_req_response response)
{
    unsigned index = 0;

    while (index < gatt_connect_observer_registry.number_registered)
    {
        const gatt_connect_observer_callback_t * callback = gatt_connect_observer_registry.callbacks[index];

        if (callback->OnDisconnectRequested)
        {
            callback->OnDisconnectRequested(cid, response);
        }
        index++;
    }
}

void GattConnect_ObserverNotifyOnEncryptionChanged(gatt_cid_t cid, bool encrypted)
{
    unsigned index = 0;

    while (index < gatt_connect_observer_registry.number_registered)
    {
        const gatt_connect_observer_callback_t * callback = gatt_connect_observer_registry.callbacks[index];

        if (callback->OnEncryptionChanged)
        {
            callback->OnEncryptionChanged(cid, encrypted);
        }
        index++;
    }
}

void GattConnect_ObserverNotifyOnMtuExchanged(gatt_cid_t cid, uint16 mtu)
{
    unsigned index = 0;

    while (index < gatt_connect_observer_registry.number_registered)
    {
        const gatt_connect_observer_callback_t * callback = gatt_connect_observer_registry.callbacks[index];

        if (callback->OnMtuExchanged)
        {
            callback->OnMtuExchanged(cid, mtu);
        }
        index++;
    }
}

void GattConnect_ObserverNotifyOnPairingComplete(gatt_cid_t cid)
{
    unsigned index = 0;

    while (index < gatt_connect_observer_registry.number_registered)
    {
        const gatt_connect_observer_callback_t * callback = gatt_connect_observer_registry.callbacks[index];

        if (callback->OnPairingComplete)
        {
            callback->OnPairingComplete(cid);
        }
        index++;
    }
}

unsigned GattConnect_ObserverGetNumberDisconnectReqCallbacksRegistered(void)
{
    unsigned index = 0;
    unsigned number_callbacks = 0;

    while (index < gatt_connect_observer_registry.number_registered)
    {
        const gatt_connect_observer_callback_t * callback = gatt_connect_observer_registry.callbacks[index];

        if (callback->OnDisconnectRequested)
        {
            number_callbacks++;
        }
        index++;
    }
    
    return number_callbacks;
}

void GattConnect_ObserverInit(void)
{
    memset(&gatt_connect_observer_registry, 0, sizeof(gatt_connect_observer_registry));
#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
    gattConnect_DbObserverInit();
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */
}
